#ifndef _PE_H_
#define _PE_H_

#include <iostream>
#include <fstream>
#include <cassert>
#include <string>
#include <vector>
#include <regex>
#include <queue>

#include <unordered_map>
#include "cache.h"
#include "hardware_model.h"
#include "option_parser.h"
#include "icnt_wrapper.h"
#include "types.h"

#define READ_PACKET_SIZE 8
// WRITE_PACKET_SIZE: bytes: 6 address, 2 miscelaneous.
#define WRITE_PACKET_SIZE 8
#define WRITE_MASK_SIZE 8

using namespace std;

class SimulatorConfig;

class PEConfig
{
public:
	PEConfig() : m_L1_ins_config(), m_L1_data_config()
	{
		m_ejection_buffer_size = 0;
		m_mem_unit_ports = 0;
		m_valid = false;
	}
	void init()
	{
		m_L1_ins_config.init();
		m_L1_data_config.init();
		m_valid = true;
	}
	void reg_options(option_parser_t opp);
	//uint32_t mem2device(uint32_t memid) const { return memid+; }
	mutable CacheConfig m_L1_ins_config;
	mutable CacheConfig m_L1_data_config;

	uint32_t m_ejection_buffer_size;
	uint32_t m_mem_unit_ports;
	bool m_valid;
};

class Uint32Buffer
{
public:
	bool miss_pending;
	bool valid;
	Uint32Buffer() : miss_pending(false), valid(false) {}
};

class PEMemFetchAllocator : public MemFetchAllocator
{
public:
	PEMemFetchAllocator(uint32_t pe_id, const MemoryConfig *config)
	{
		m_pe_id = pe_id;
		m_memory_config = config;
	}
	MemFetch *alloc(addr_t addr, MemAccessType type, uint32_t size,
					bool wr, cycle_t cycle) const
	{
		MemAccess access(type, addr, size, wr);
		MemFetch *mf =new MemFetch(access, wr ? WRITE_PACKET_SIZE : READ_PACKET_SIZE, NULL, m_pe_id, m_memory_config, cycle);
		return mf;
	}
	MemFetch *alloc(const MemAccess &access, cycle_t cycle) const
	{
		MemFetch *mf = new MemFetch(
			access,
			access.is_write() ? WRITE_PACKET_SIZE : READ_PACKET_SIZE,NULL,
			m_pe_id, m_memory_config, cycle);
		return mf;
	}

private:
	uint32_t m_pe_id;
	uint32_t m_cluster_id;
	const MemoryConfig *m_memory_config;
};



class LDSTUnit{
public:
    LDSTUnit(PE *pe);
    void cycle();
     
    void fill( MemFetch *mf );
    void flush();
    void writeback();
    bool response_buffer_full();
    float get_cache_hit_rate()
    {
        printf("cache hit:%d, cache access:%d, cache hit rate:%f\n", num_access_hit,
                    num_access, num_access_hit*1.0/num_access);
    }
protected:

   //virtual MemStageStallType process_cache_access( cache_t* cache,
                                                      //new_addr_type address,
                                                      //warp_inst_t &inst,
                                                      //std::list<cache_event>& events,
                                                      //mem_fetch *mf,
                                                      //enum cache_request_status status );
   //mem_stage_stall_type process_memory_access_queue( cache_t *cache, warp_inst_t &inst );

   
   const MemoryConfig *m_memory_config;
   class MemFetchInterface *m_icnt;
   PEMemFetchAllocator *m_mf_allocator;

   class PE *m_pe;
   const PEConfig *m_config;
   unsigned m_pe_id;

   L1Cache *m_L1D; // data cache
   std::list<MemFetch*> m_response_fifo;
   uint32_t idx1;
   uint32_t idx2;
   uint32_t num_access;
   uint32_t num_access_hit;
//   Scoreboard *m_scoreboard;
};

 enum class InsType:int
    {
        LUI, ADDI, LW, SW, ADD,BNE, MAX
    };
    enum class OpType:int
    {
        REG, REGIMM, NONE, MAX
    };

class Instruction
{
public:
   
    struct Operand
    {
        int reg_idx;
        int val;
		int imm;
		bool is_dest;
        string str;
        OpType type;
        Operand(bool is_dest_=false)
        {
			is_dest=is_dest_;
            val=0;
            type=OpType::MAX;
        }
        void init()
        {
            type=OpType::NONE;
            reg_idx=-1;
        }
        void init(string str_)
        {
            assert(str_!="");
            str=str_;
            if(str.find("a")!=string::npos && str.find("(")==string::npos)
                type=OpType::REG;
            else if(str.find("(")!=string::npos)
                type=OpType::REGIMM;

            set_idx();
        }
        void set_idx()
        {
            assert(str!="");
            
            if(type==OpType::REG)
            {
                string tmp = str.substr(1,str.size()-1);
                reg_idx=stoi(tmp);
            }
            else if(type==OpType::REGIMM)
            {
                uint32_t idx1=str.find("(");
                string str1=str.substr(0,idx1);
                string str2=str.substr(idx1+2,str.size()-1);
                sscanf(str1.c_str(), "%x", &imm);   
                reg_idx=stoi(str2);   
            }
        }
    };

    InsType type;
    string ins;

    uint32_t exec_cycles;
    uint32_t writeback_cycles;
    uint32_t pc;
    uint32_t imm;
    bool exec_completed;
    bool writeback_completed;
    bool load_completed;
    bool no_need_mac;
	bool valid;
    bool is_write;
    bool is_executing;
    bool is_fetching;
    Operand src1;
    Operand src2;
    Operand dst;
    uint32_t addr;
    int result;


            
    Instruction(string ins_, uint32_t pc_):dst(true)
    {
        ins=ins_;
        pc=pc_;
        type=InsType::MAX;   
        load_completed=false;
        exec_completed=false;
        no_need_mac=false;
        writeback_completed=false;
        is_executing=false;
        valid=false;
    }
    
    Instruction()
    {
        ins="";
        pc=0;
        type=InsType::MAX;   
        load_completed=false;
        exec_completed=false;
        no_need_mac=false;
        writeback_completed=false;
        is_executing=false;
        valid=false;
    }
    void decode()
    {
        vector<string> ins_vec;
        string result;
        stringstream input(ins);
        while(input>>result)
            ins_vec.push_back(result);
        //for(int i=0;i<ins_vec.size();i++)
            //cout<<ins_vec[i]<<endl;
        if(ins_vec[0]=="lui")
        {
            exec_cycles=1;
            type=InsType::LUI;
            src1.init();
            src2.init();
            dst.init(ins_vec[1]);
            imm=std::stoi(ins_vec[2], nullptr, 16);
        }
        else if(ins_vec[0]=="addi")
        {
            type=InsType::ADDI; 
            writeback_cycles=1;
            exec_cycles=1;
            src1.init(ins_vec[1]);
            src2.init();
            imm=std::stoi(ins_vec[3], nullptr, 16);
            dst.init(ins_vec[1]);
        }
        else if(ins_vec[0]=="lw")
        {
            type=InsType::LW; 
            is_write=false;
            src1.init(ins_vec[2]);
            src2.init();
            dst.init(ins_vec[1]);
            imm=src1.imm;

        }
        else if(ins_vec[0]=="sw")
        {
            type=InsType::SW; 
            writeback_cycles=1;
            is_write=true;
            src1.init(ins_vec[2]);
            src2.init(ins_vec[1]);
            imm=src1.imm;
        }
        else if(ins_vec[0]=="bne")
        {
            type=InsType::BNE; 
            writeback_cycles=1;
            src1.init(ins_vec[1]);
            src2.init(ins_vec[2]);
            imm=std::stoi(ins_vec[3], nullptr, 16);
        }
        else if(ins_vec[0]=="add")
        {
            type=InsType::ADD; 
            writeback_cycles=1;
            src1.init(ins_vec[3]);
            src2.init(ins_vec[2]);
            dst.init(ins_vec[1]);
        }
    }
};

class PE
{
public:

	Instruction *m_ins_fetched;

	void cycle();
	void get_val_stage();
	void get_col_stage();
	void get_eor_stage();
	void mac_stage();
	void writeback();
	void execute();
	void fetch_operand();
	void issue();
	void decode();
	void fetch();
	void load_code();
	void init(PEConfig *pe_config);
	bool active();
	void accept_fetch_response(MemFetch *mf);
	bool icnt_injection_buffer_full(uint32_t size, bool write);
	void icnt_inject_request_packet(MemFetch *mf);
	void icnt_cycle();
	bool fetch_unit_response_buffer_full() const { return false; }
    bool ldst_unit_response_buffer_full();
    float get_cache_hit_rate()
    {
        return m_ldst_unit->get_cache_hit_rate();
    }
	PE(uint32_t pe_id, SIMULATOR *simulator, const SimulatorConfig *simulator_config);

	ReadOnlyCache *m_L1;
	MemFetchInterface *m_icnt;
	PEMemFetchAllocator *m_mem_fetch_allocator;

	uint32_t m_pe_id;
    uint32_t num_ins;
	SIMULATOR *m_simulator;
	bool m_pe_sim_done;
    std::queue<Instruction*> ldst_ins_queue;
    std::queue<Instruction*> alu_ins_queue;
    std::queue<Instruction*> writeback_ins_queue;
    std::queue<Instruction*> fetch_ins_queue;
    std::queue<Instruction*> fetch_operand_ins_queue;
    std::queue<Instruction*> decode_ins_queue;
    std::queue<Instruction*> issue_ins_queue;
    bool scoreboard[32];
	int registers[32];

	std::list<MemFetch *> m_response_fifo;

	uint32_t pc;

	const PEConfig *m_pe_config;
	const CacheConfig *m_ins_cache_config;
	const CacheConfig *m_data_cache_config;
	LDSTUnit *m_ldst_unit;
	const MemoryConfig *m_mem_config;
	std::vector<std::string>  ins_buffer;
    uint32_t loop_cnt=0;
    uint32_t num_executed_ins;

    uint32_t start_pc_address;

};

class PEMemoryInterface : public MemFetchInterface
{
public:
	PEMemoryInterface(PE *pe)
	{
		m_pe = pe;
	}
	virtual bool full(uint32_t size, bool write) const
	{
		return m_pe->icnt_injection_buffer_full(size, write);
	}
	virtual void push(MemFetch *mf)
	{
		m_pe->icnt_inject_request_packet(mf);
	}

private:
	PE *m_pe;
};

#endif